#include "imageio.h"
#include "spectrum.h"
#include "console.h"
#include "memory.h"


// ImageIO Local Declarations
// Support format including
// EXR TGA PFM BMP JPEG PNG TIFF

// TO DO: 
// Damning testing & More formats:HDR et, al.


// EXR format
static Spectrum *ReadImageEXR(const string &name, int *width, int *height);
static void WriteImageEXR(const string &name, float *pixels,
        float *alpha, int xRes, int yRes,
        int totalXRes, int totalYRes,
        int xOffset, int yOffset);

// TGA format
static void WriteImageTGA(const string &name, float *pixels,
        float *alpha, int xRes, int yRes,
        int totalXRes, int totalYRes,
        int xOffset, int yOffset);
static Spectrum *ReadImageTGA(const string &name, int *w, int *h);

// PFM format
static Spectrum *ReadImagePFM(const string &filename, int *xres, int *yres);
static bool WriteImagePFM(const string &filename, const float *rgb, int xres, int yres);


// BMP format
static Spectrum *ReadImageBMP(const string &filename, int *xres, int *yres);
static void WriteImageBMP(const string &filename, float *pixels, int xres, int yres);


// JPEG format
static Spectrum *ReadImageJPG(const string &filename, int *xres, int *yres);
static void WriteImageJPG(const string &filename, float *pixels, int xres, int yres);


// PNG format
static Spectrum *ReadImagePNG(const string &filename, int *xres, int *yres);
static void WriteImagePNG(const string &filename, float *pixels, int xres, int yres);


// TIFF format
static Spectrum *ReadImageTIF(const string &filename, int *xres, int *yres);
static void WriteImageTIF(const string &filename, float *pixels, int xres, int yres);

static Spectrum *ReadImageHDR(const string &filename, int *xres, int *yres);
static void WriteImageHDR(const string &filename, float *pixels, int xres, int yres);

// ImageIO Function Definitions
Spectrum *ReadImage(const string &name, int *width, int *height) {
    if (name.size() >= 5) {
        uint32_t suffixOffset = name.size() - 4;

        if (!strcmp(name.c_str() + suffixOffset, ".exr") ||
            !strcmp(name.c_str() + suffixOffset, ".EXR"))
             return ReadImageEXR(name, width, height);

        if (!strcmp(name.c_str() + suffixOffset, ".tga") ||
            !strcmp(name.c_str() + suffixOffset, ".TGA"))
            return ReadImageTGA(name, width, height);

        if (!strcmp(name.c_str() + suffixOffset, ".pfm") ||
            !strcmp(name.c_str() + suffixOffset, ".PFM"))
            return ReadImagePFM(name, width, height);

		if (!strcmp(name.c_str() + suffixOffset, ".bmp") ||
			!strcmp(name.c_str() + suffixOffset, ".BMP"))
			return ReadImageBMP(name, width, height);

		if (!strcmp(name.c_str() + suffixOffset, ".jpg") ||
			!strcmp(name.c_str() + suffixOffset, ".JPG"))
			return ReadImageJPG(name, width, height);

		if (!strcmp(name.c_str() + suffixOffset, ".png") ||
			!strcmp(name.c_str() + suffixOffset, ".PNG"))
			return ReadImagePNG(name, width, height);

		if (!strcmp(name.c_str() + suffixOffset, ".tif") ||
			!strcmp(name.c_str() + suffixOffset, ".TIF"))
			return ReadImageTIF(name, width, height);
    }
    Error("Can't determine image file type from suffix of filename \"%s\"",
          name.c_str());
    Spectrum *ret = new Spectrum[1];
    ret[0] = 0.5f;
    *width = *height = 1;
    return ret;
}


void WriteImage(const string &name, float *pixels, float *alpha, int xRes,
                int yRes, int totalXRes, int totalYRes, int xOffset, int yOffset) {
    if (name.size() >= 5) {
        uint32_t suffixOffset = name.size() - 4;

        if (!strcmp(name.c_str() + suffixOffset, ".exr") ||
            !strcmp(name.c_str() + suffixOffset, ".EXR")) {
             WriteImageEXR(name, pixels, alpha, xRes, yRes, totalXRes,
                           totalYRes, xOffset, yOffset);
             return;
        }

        if (!strcmp(name.c_str() + suffixOffset, ".tga") ||
            !strcmp(name.c_str() + suffixOffset, ".TGA")) {
            WriteImageTGA(name, pixels, alpha, xRes, yRes, totalXRes,
                          totalYRes, xOffset, yOffset);
            return;
        }
        if (!strcmp(name.c_str() + suffixOffset, ".pfm") ||
            !strcmp(name.c_str() + suffixOffset, ".PFM")) {
            WriteImagePFM(name, pixels, xRes, yRes);
            return;
        }
		if (!strcmp(name.c_str() + suffixOffset, ".bmp") ||
			!strcmp(name.c_str() + suffixOffset, ".BMP")) {
			WriteImageBMP(name, pixels, xRes, yRes);
			return;
		}
		if (!strcmp(name.c_str() + suffixOffset, ".jpg") ||
			!strcmp(name.c_str() + suffixOffset, ".JPG")) {
			WriteImageJPG(name, pixels, xRes, yRes);
			return;
		}
		if (!strcmp(name.c_str() + suffixOffset, ".png") ||
			!strcmp(name.c_str() + suffixOffset, ".PNG")) {
			WriteImagePNG(name, pixels, xRes, yRes);
			return;
		}
		if (!strcmp(name.c_str() + suffixOffset, ".tif") ||
			!strcmp(name.c_str() + suffixOffset, ".TIF")) {
			WriteImageTIF(name, pixels, xRes, yRes);
			return;
		}
    }
    Error("Can't determine image file type from suffix of filename \"%s\"",
          name.c_str());
}



#define hypotf hypot // For the OpenEXR headers

#include <ImfInputFile.h>
#include <ImfRgbaFile.h>
#include <ImfChannelList.h>
#include <ImfFrameBuffer.h>
#include <half.h>
using namespace Imf;
using namespace Imath;

// EXR Function Definitions
static Spectrum *ReadImageEXR(const string &name, int *width, int *height) {
    try {
    InputFile file(name.c_str());
    Box2i dw = file.header().dataWindow();
    *width  = dw.max.x - dw.min.x + 1;
    *height = dw.max.y - dw.min.y + 1;

    half *rgb = new half[3 * *width * *height];

    FrameBuffer frameBuffer;
    frameBuffer.insert("R", Slice(HALF, (char *)rgb,
        3*sizeof(half), *width * 3 * sizeof(half), 1, 1, 0.0));
    frameBuffer.insert("G", Slice(HALF, (char *)rgb+sizeof(half),
        3*sizeof(half), *width * 3 * sizeof(half), 1, 1, 0.0));
    frameBuffer.insert("B", Slice(HALF, (char *)rgb+2*sizeof(half),
        3*sizeof(half), *width * 3 * sizeof(half), 1, 1, 0.0));

    file.setFrameBuffer(frameBuffer);
    file.readPixels(dw.min.y, dw.max.y);

    Spectrum *ret = new Spectrum[*width * *height];
    for (int i = 0; i < *width * *height; ++i) {
        float frgb[3] = { rgb[3*i], rgb[3*i+1], rgb[3*i+2] };
        ret[i].FromRGB(frgb);
    }
    //delete[] rgb;
	SAFE_DELETE_ARRAY(rgb);
    Debug("Read EXR image %s (%d x %d)", name.c_str(), *width, *height);
		return ret;
    } catch (const std::exception &e) {
        Error("Unable to read image file \"%s\": %s", name.c_str(),
            e.what());
        return NULL;
    }
}


static void WriteImageEXR(const string &name, float *pixels,
        float *alpha, int xRes, int yRes,
        int totalXRes, int totalYRes,
        int xOffset, int yOffset) {
	
	if(pixels == NULL || xRes ==0 || yRes == 0) {
		Warning("No Image Data to write\n");
		return;
	}
    Rgba *hrgba = new Rgba[xRes * yRes];
    for (int i = 0; i < xRes * yRes; ++i)
        hrgba[i] = Rgba(pixels[3*i], pixels[3*i+1], pixels[3*i+2],
                        alpha ? alpha[i]: 1.f);

    Box2i displayWindow(V2i(0,0), V2i(totalXRes-1, totalYRes-1));
    Box2i dataWindow(V2i(xOffset, yOffset), V2i(xOffset + xRes - 1, yOffset + yRes - 1));

    try {
        RgbaOutputFile file(name.c_str(), displayWindow, dataWindow, WRITE_RGBA);
        file.setFrameBuffer(hrgba - xOffset - yOffset * xRes, 1, xRes);
        file.writePixels(yRes);
    }
    catch (const std::exception &e) {
        Error("Unable to write image file \"%s\": %s", name.c_str(),
            e.what());
    }

    delete[] hrgba;
}


// TGA Function Definitions
/**\file
 *\section License
 * License: GPL
 * Online License Link: http://www.gnu.org/licenses/gpl.html
 *
 *\author Copyright (c) 2003-2009 Jaakko Keranen <jaakko.keranen@iki.fi>
 *\author Copyright (c) 2009 Daniel Swanson <danij@dengine.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

/**
 * gl_tga.c: TGA file format (TARGA) reader/writer.
 */

// HEADER FILES ------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned char byte;

typedef unsigned char uchar;

// MACROS ------------------------------------------------------------------

#undef SHORT
#ifdef __BIG_ENDIAN__
#define SHORT(x)            shortSwap(x)
# else // Little-endian.
#define SHORT(x)            (x)
#endif

// TYPES -------------------------------------------------------------------

typedef struct {
    uchar           idLength; // Identification field size in bytes.
    uchar           colorMapType; // Type of the color map.
    uchar           imageType; // Image type code.
} tga_header_t;

// Color map specification.
typedef struct {
    int16_t         index; // Index of first color map entry.
    int16_t         length; // Number of color map entries.
    uchar           entrySize; // Number of bits in a color map entry (16/24/32).
} tga_colormapspec_t;

// Image specification.
typedef struct {
    int16_t         xOrigin; // X coordinate of lower left corner.
    int16_t         yOrigin; // Y coordinate of lower left corner.
    int16_t         width; // Width of the image in pixels.
    int16_t         height; // Height of the image in pixels.
    uchar           pixelDepth; // Number of bits in a pixel (16/24/32).
    uchar           attributeBits;
} tga_imagespec_t;


#ifdef __BIG_ENDIAN__
static int16_t shortSwap(int16_t n)
{
    return ((n & 0xff) << 8) | ((n & 0xff00) >> 8);
}


#endif

static bool writeByte(FILE* f, uchar b)
{
    return (fwrite(&b, 1, 1, f) == 1);
}



static bool writeShort(FILE* f, int16_t s)
{
    int16_t             v = SHORT(s);
    return (fwrite(&v, sizeof(v), 1, f) == 1);
}



static uchar readByte(FILE* f)
{
    uchar               v;
    fread(&v, sizeof(v), 1, f);
    return v;
}



static int16_t readShort(FILE* f)
{
    int16_t             v;
    fread(&v, sizeof(v), 1, f);
    return v;
}



/**
 * @param idLength      Identification field size in bytes (max 255).
 *                      @c 0 indicates no identification field.
 * @param colorMapType  Type of the color map, @c 0 or @c 1:
 *                      @c 0 = color map data is not present.
 *                      @c 1 = color map data IS present.
 * @param imageType     Image data type code, one of:
 *                      @c 0 = no image data is present.
 *                      @c 1 = uncompressed, color mapped image.
 *                      @c 2 = uncompressed, true-color image.
 *                      @c 3 = uncompressed, grayscale image.
 *                      @c 9 = run-length encoded, color mapped image.
 *                      @c 10 = run-length encoded, true-color image.
 *                      @c 11 = run-length encoded, grayscale image.
 * @param file          Handle to the file to be written to.
 */
static void writeHeader(uchar idLength, uchar colorMapType, uchar imageType,
                        FILE* file)
{
    writeByte(file, idLength);
    writeByte(file, colorMapType? 1 : 0);
    writeByte(file, imageType);
}



static void readHeader(tga_header_t* dst, FILE* file)
{
    dst->idLength = readByte(file);
    dst->colorMapType = readByte(file);
    dst->imageType = readByte(file);
}



/**
 * @param index         Index of first color map entry.
 * @param length        Total number of color map entries.
 * @param entrySize     Number of bits in a color map entry; 15/16/24/32.
 * @param file          Handle to the file to be written to.
 */
static void writeColorMapSpec(int16_t index, int16_t length,
                              uchar entrySize, FILE* file)
{
    writeShort(file, index);
    writeShort(file, length);
    writeByte(file, entrySize);
}



static void readColorMapSpec(tga_colormapspec_t* dst, FILE* file)
{
    dst->index = readShort(file);
    dst->length = readShort(file);
    dst->entrySize = readByte(file);
}



/**
 * @param xOrigin       X coordinate of lower left corner.
 * @param yOrigin       Y coordinate of lower left corner.
 * @param width         Width of the image in pixels.
 * @param height        Height of the image in pixels.
 * @param pixDepth      Number of bits per pixel, one of; 16/24/32.
 * @param file          Handle to the file to be written to.
 */
static void writeImageSpec(int16_t xOrigin, int16_t yOrigin,
                           int16_t width, int16_t height, uchar pixDepth,
                           FILE* file)
{
    writeShort(file, xOrigin);
    writeShort(file, yOrigin);
    writeShort(file, width);
    writeShort(file, height);
    writeByte(file, pixDepth);

    /**
     * attributeBits:4; // Attribute bits associated with each pixel.
     * reserved:1; // A reserved bit; must be 0.
     * screenOrigin:1; // Location of screen origin; must be 0.
     * dataInterleave:2; // TGA_INTERLEAVE_*
     */
    writeByte(file, 0);
}



static void readImageSpec(tga_imagespec_t* dst, FILE* file)
{
    dst->xOrigin = readShort(file);
    dst->yOrigin = readShort(file);
    dst->width = readShort(file);
    dst->height = readShort(file);
    dst->pixelDepth = readByte(file);
    dst->attributeBits = readByte(file);
}


/**
 * Save the rgb8888 buffer as Targa 24.
 *
 * @param filename      Path to the file to be written to (need not exist).
 * @param w             Width of the image in pixels.
 * @param h             Height of the image in pixels.
 * @param buf           Ptr to the image data to be written.
 *
 * @return              Non-zero iff successful.
 */
void WriteImageTGA(const string &name, float *pixels,
        float *alpha, int xRes, int yRes,
        int totalXRes, int totalYRes,
        int xOffset, int yOffset)
{
    if(pixels == NULL || xRes ==0 || yRes == 0) {
		Warning("No Image Data to write\n");
		return;
	}
	
	FILE*               file;
    uchar*              outBuf;

    if ((file = fopen(name.c_str(), "wb")) == NULL) {
        Error("Unable to open output filename \"%s\"", name.c_str());
        return;
    }

    // No identification field, no color map, Targa type 2 (unmapped RGB).
    writeHeader(0, 0, 2, file);
    writeColorMapSpec(0, 0, 0, file);
    writeImageSpec(0, 0, xRes, yRes, 24, file);

    // The save format is BGR.
    outBuf = (uchar *)malloc(xRes * yRes * 3);
    uchar *dst = outBuf;
    for (int y = yRes-1; y >= 0; --y) {
        for (int x = 0; x < xRes; ++x) {
//#define TO_BYTE(v) (uint8_t(Clamp(255.f * powf((v), 1.f/2.3f), 0.f, 255.f)))
            dst[0] = TO_BYTE(pixels[3*(y*xRes+x)+2]);
            dst[1] = TO_BYTE(pixels[3*(y*xRes+x)+1]);
            dst[2] = TO_BYTE(pixels[3*(y*xRes+x)+0]);
            dst += 3;
        }
    }
    if (fwrite(outBuf, 1, 3 * xRes * yRes, file) != uint32_t(3*xRes*yRes))
        Error("Error writing TGA image file \"%s\"", name.c_str());
    free(outBuf);
    fclose(file);
}




/**
 * Loads a TGA image (not the RLE types though)
 */
static Spectrum *ReadImageTGA(const string &name, int *width, int *height)
{
    int                 x, y, pixbytes;
    tga_header_t        header;
    tga_colormapspec_t  colorMapSpec;
    tga_imagespec_t     imageSpec;
    uchar*              srcBuf;
    const uchar*        src;

    FILE *file = fopen(name.c_str(), "rb");
    if (!file) {
        Error("Unable to open TGA file \"%s\"", name.c_str());
        return NULL;
    }

    // Read and check the header.
    readHeader(&header, file);
    readColorMapSpec(&colorMapSpec, file);
    readImageSpec(&imageSpec, file);

    if (((imageSpec.attributeBits & 0xf) != 8 &&  // num attribute bits
         (imageSpec.attributeBits & 0xf) != 0) ||
        ((imageSpec.attributeBits & 0xc0) != 0) || // no interleaving
        (header.imageType == 2 &&
          (imageSpec.pixelDepth != 32 && imageSpec.pixelDepth != 24)) ||
        (header.imageType == 3 &&
          (imageSpec.pixelDepth != 8)) ||
        (header.imageType != 2 && header.imageType != 3)) {
        Error("ReadImageTGA: I don't know this format "
              "(type=%i pxsize=%i abits=%i)", header.imageType,
              imageSpec.pixelDepth,
              imageSpec.attributeBits);
        fclose(file);
        return NULL;
    }

    *width = imageSpec.width;
    *height = imageSpec.height;

    // Determine format.
    if (imageSpec.pixelDepth == 32)
        pixbytes = 4;
    else if (imageSpec.pixelDepth == 24)
        pixbytes = 3;
    else {
        Assert(imageSpec.pixelDepth == 8);
        pixbytes = 1;
    }

    // Read the pixel data.
    int size = *width * *height * pixbytes;
    srcBuf = (uchar *)malloc(size);
    if (fread(srcBuf, 1, size, file) != (uint32_t)size) {
        Error("Premature end-of-file when reading TGA image \"%s\"", name.c_str());
        free(srcBuf);
        fclose(file);
        return NULL;
    }

    // "Unpack" the pixels (origin in the lower left corner).
    // TGA pixels are in BGRA format.
    src = srcBuf;
    Spectrum *ret = new Spectrum[*width * *height];
    Spectrum *dst = ret;
    for (y = *height - 1; y >= 0; y--)
        for (x = 0; x < *width; x++) {
            if (pixbytes == 1)
                *dst++ = Spectrum((*src++) * INV_255);
            else {
                float c[3];
                c[2] = (*src++) * INV_255;
                c[1] = (*src++) * INV_255;
                c[0] = (*src++) * INV_255;
				(*dst).FromRGB(c);
				dst++;
                //*dst++ = Spectrum::FromRGB(c);
                if (pixbytes == 4)
                    ++src;
            }
    }

    bool flipH = ((imageSpec.attributeBits & 0x10) == 0x10);
    bool flipV = ((imageSpec.attributeBits & 0x20) == 0x20);
    if (flipH) {
        for (y = 0; y < *height; ++y)
            for (x = 0; x < *width / 2; ++x)
                swap(ret[y * *width + x], ret[y * *width + (*width - 1 - x)]);
    }
    if (flipV) {
        for (y = 0; y < *height/2; ++y)
            for (x = 0; x < *width; ++x)
                swap(ret[y * *width + x], ret[(*height - 1 - y) * *width + x]);
    }
    free(srcBuf);
    fclose(file);
    Debug("Read TGA image %s (%d x %d)", name.c_str(), *width, *height);
    return ret;
}



// PFM Function Definitions
/*
 * PFM reader/writer code courtesy Jiawen "Kevin" Chen (http://people.csail.mit.edu/jiawen/)
 */

static bool hostLittleEndian = true;

#define BUFFER_SIZE 80

static inline int isWhitespace( char c )
{
    return c == ' ' || c == '\n' || c == '\t';
}



// reads a "word" from the fp and puts it into buffer
// and adds a null terminator
// i.e. it keeps reading until a whitespace is reached
// returns the number of characters read
// *not* including the whitespace
// return -1 on an error
static int readWord(FILE* fp, char* buffer, int bufferLength) {
    int n;
    char c;

    if (bufferLength < 1)
        return -1;

    n = 0;
    c = fgetc( fp );
    while( c != EOF && !isWhitespace( c ) && n < bufferLength ) {
        buffer[ n ] = c;
        ++n;
        c = fgetc( fp );
    }

    if( n < bufferLength ) {
        buffer[ n ] = '\0';
        return n;
    }

    return -1;
}



static Spectrum *ReadImagePFM(const string &filename, int *xres, int *yres) {
    float *data = NULL;
    Spectrum *rgb = NULL;
    char buffer[ BUFFER_SIZE ];
    unsigned int nFloats;
    int nChannels, width, height;
    float scale;
    bool fileLittleEndian;

    FILE *fp = fopen(filename.c_str(), "rb");
    if (!fp)
        goto fail;

    // read either "Pf" or "PF"
    if (readWord( fp, buffer, BUFFER_SIZE ) == -1)
        goto fail;

    if (strcmp( buffer, "Pf" ) == 0)
        nChannels = 1;
    else if (strcmp( buffer, "PF" ) == 0)
        nChannels = 3;
    else
        goto fail;

    // read the rest of the header
    // read width
    if (readWord( fp, buffer, BUFFER_SIZE ) == -1)
        goto fail;
    width = atoi( buffer );
    *xres = width;

    // read height
    if (readWord( fp, buffer, BUFFER_SIZE ) == -1)
        goto fail;
    height = atoi( buffer );
    *yres = height;

    // read scale
    if (readWord( fp, buffer, BUFFER_SIZE ) == -1)
        goto fail;
    sscanf( buffer, "%f", &scale );

    // read the data
    nFloats = nChannels * width * height;
    data = new float[nFloats];
    if (fread(data, sizeof( float ), nFloats, fp ) != nFloats)
        goto fail;

    // apply endian conversian and scale if appropriate
    fileLittleEndian = (scale < 0.f);
    if (hostLittleEndian ^ fileLittleEndian) {
        uint8_t bytes[4];
        for (unsigned int i = 0; i < nFloats; ++i) {
            memcpy(bytes, &data[i], 4);
            swap(bytes[0], bytes[3]);
            swap(bytes[1], bytes[2]);
            memcpy(&data[i], bytes, 4);
        }
    }
    if (fabsf(scale) != 1.f)
        for (unsigned int i = 0; i < nFloats; ++i)
            data[i] *= fabsf(scale);

    // create RGBs...
    rgb = new Spectrum[width*height];
    if (nChannels == 1) {
        for (int i = 0; i < width * height; ++i)
            rgb[i] = data[i];
    }
    else {
        for (int i = 0; i < width * height; ++i)
            rgb[i].FromRGB(&data[3*i]);
    }

    //delete[] data;
	SAFE_DELETE_ARRAY(data);
    fclose(fp);
    return rgb;

 fail:
    Error("Error reading PFM file \"%s\"", filename.c_str());
    fclose(fp);
	SAFE_DELETE_ARRAY(data);
	SAFE_DELETE_ARRAY(rgb);
    //delete[] data;
    //delete[] rgb;
    return NULL;
}




static bool WriteImagePFM(const string &filename, const float *rgb,
                          int width, int height) {
    
	if(rgb == NULL || width ==0 || height == 0) {
		Warning("No Image Data to write\n");
		return false;
	}

	FILE* fp;
    unsigned int nFloats;
    float scale;

    fp = fopen(filename.c_str(), "wb");
    if (!fp) {
        Error("Unable to open output PFM file \"%s\"", filename.c_str());
        return false;
    }

    // only write 3 channel PFMs here...
    if (fprintf(fp, "PF\n") < 0)
        goto fail;

    // write the width and height, which must be positive
    if (fprintf(fp, "%d %d\n", width, height) < 0)
        goto fail;

    // write the scale, which encodes endianness
    scale = hostLittleEndian ? -1.f : 1.f;
    if (fprintf(fp, "%f\n", scale) < 0)
        goto fail;

    // write the data
    nFloats = 3 * width * height;
    if (fwrite(rgb, sizeof(float), nFloats, fp) < nFloats)
        goto fail;

    fclose(fp);
    return true;

 fail:
    Error("Error writing PFM file \"%s\"", filename.c_str());
    fclose(fp);
    return false;
}

// BMP format
static Spectrum *ReadImageBMP(const string &filename, int *xres, int *yres) {
#if defined(WIN32)
#pragma pack(push, 1)
#endif
	struct 
#if !defined(WIN32)
		__attribute__((__packed__))
#endif
	{
		uint16_t magic;
		uint32_t size;
		uint16_t creator1, creator2;
		uint32_t offset;
	} BMPFileHeader;

	struct 
#if !defined(WIN32)
		__attribute__((__packed__))
#endif
	{
		uint32_t header_sz;
		uint32_t width;
		int32_t height;
		uint16_t nplanes;
		uint16_t bitspp;
		uint32_t compress_type;
		uint32_t bmp_bytesz;
		uint32_t hres;
		uint32_t vres;
		uint32_t ncolors;
		uint32_t nimpcolors;
	} DIBHeader;
#if defined(WIN32)
#pragma pack(pop)
#endif

	Assert(sizeof(BMPFileHeader) == 14);
	Assert(sizeof(DIBHeader) == 40);

	FILE *fp = fopen(filename.c_str(),"rb");
	if (!fp) {
		Error("Unable to open BMP file \"%s\" to read\n", filename.c_str());
		return NULL;
	}

	fread(&BMPFileHeader, sizeof(BMPFileHeader), 1, fp);
	//stream->read(&BMPFileHeader, sizeof(BMPFileHeader));

	if (memcmp(&BMPFileHeader.magic, "BM", 2) != 0)
		Error("Unsupported BMP format encountered (invalid file header)!");

	fread(&DIBHeader, sizeof(DIBHeader), 1, fp);
	//stream->read(&DIBHeader, sizeof(DIBHeader));

	if (DIBHeader.header_sz != 40 || DIBHeader.nplanes != 1)
		Error("Unsupported BMP format encountered (strange DIB header)!");

	if (DIBHeader.ncolors != 0)
		Error("Only BMP images without a palette are supported for now");

	if (DIBHeader.bitspp != 8 && DIBHeader.bitspp != 24)
		Error("Only 8- and 24-bit BMP images are supported for now");

	if (DIBHeader.compress_type != 0)
		Error("Only uncompressed BMP images are supported for now");

	*xres = DIBHeader.width;
	*yres = std::abs(DIBHeader.height);
	int bpp = DIBHeader.bitspp;

	int size = *xres * *yres * (bpp / 8);
	unsigned char *data = AllocAligned<unsigned char>(size);

	int nChannels = bpp / 8;
	int lineWidth = *xres * nChannels; 
	int paddedLineWidth = lineWidth + lineWidth % 4;

	uint8_t *line = new uint8_t[paddedLineWidth];

	for (int y=0; y<*yres; ++y) {
		fread(line,sizeof(uint8_t), paddedLineWidth, fp);
		int targetY = y;
		if (DIBHeader.height > 0)
			targetY = *yres - 1 - y; // inverted rows
		memcpy(&data[targetY * *xres * nChannels], line, lineWidth);
		if (nChannels == 3) {
			// bgr -> rgb ?
			for (int x=0; x < *xres; ++x)
				std::swap(data[targetY * lineWidth + 3 * x], data[targetY * lineWidth + 3 * x + 2]);
		}
	}

	Spectrum *ret = new Spectrum[*xres * *yres];
	float * frgb = new float[size];

	for (int i = 0; i < size; i++) {
		frgb[i] = float(data[i]) * INV_255;
	}

	if(nChannels == 1) {
		for (int i = 0; i < size; i++) {
			ret[i] = frgb[i];
		}
	}
	else {	// nChannels == 3 
		for(int i = 0; i < *xres * *yres; i++) {
			ret[i].FromRGB(&frgb[3*i]);
		}
	}
	
	FreeAligned(data);

	SAFE_DELETE_ARRAY(line);
	SAFE_DELETE_ARRAY(frgb);
	
	fclose(fp);
	
	return ret;
}

static void WriteImageBMP(const string &filename, float *pixels, int xres, int yres) {
	if(pixels == NULL || xres ==0 || yres == 0) {
		Warning("No Image Data to write\n");
		return;
	}
	// Save the image
	BITMAPFILEHEADER head;
	BITMAPINFOHEADER info;

	FILE *fp;
	int	ptr, i, j;
	unsigned char pcrgb;

	/*if (xres == 0 || yres == 0) {
		Warning("Image width is zero!\n");
		return;
	}*/

	fp=fopen(filename.c_str(),"wb");

	if(fp == NULL)
	{
		Error("Unable to open BMP file \"%s\" to write\n",filename.c_str());
		return;
	}

	// Fill bmp header info
	head.bfType	= 0x4d42;
	// 3 channels rgb bmp file
	head.bfSize	= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+xres*yres*3; 
	head.bfReserved1= 0;

	head.bfReserved2= 0;

	head.bfOffBits	= sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

	info.biSize	= sizeof(BITMAPINFOHEADER);

	info.biWidth	= xres;

	info.biHeight	= yres;

	info.biPlanes	= 1; 

	info.biBitCount	= 24;

	info.biCompression=0; 

	info.biSizeImage=0; 

	info.biXPelsPerMeter=0; 

	info.biYPelsPerMeter=0; 

	info.biClrUsed=0; 

	info.biClrImportant=0;

	// Convert pixel value from float to BYTE
	uint8_t *data = new uint8_t[xres*yres*3];
	for(int i = 0; i < xres*yres*3; i++)
		data[i] = TO_BYTE(pixels[i]);

	fwrite(&head,sizeof(BITMAPFILEHEADER),1,fp);

	fwrite(&info,sizeof(BITMAPINFOHEADER),1,fp);

	ptr = 0;

	for( i = 0; i < yres; i ++ ) {
		for(j=0;j < xres;j ++, ptr += 3) {
			// rgb --> bgr
			fwrite(&data[ptr+2],1,1,fp);
			fwrite(&data[ptr+1],1,1,fp);
			fwrite(&data[ptr  ],1,1,fp);
		}
		
		// Aligned with 4 bytes
		j = xres*3;

		if( j%4 != 0) 
			j = 4-j%4;
		else j = 0;

		pcrgb=0;

		while(j>0)
		{	
			fwrite(&pcrgb,1,1,fp);
			j--;
		}
	}

	fclose(fp);
}


extern "C" {
#include <jpeglib.h>
#include <jerror.h>
};

#include <setjmp.h>

/* ========================== *
 *   JPEG helper functions    *
 * ========================== */

extern "C" {
	struct my_error_mgr {
		struct jpeg_error_mgr pub;	/* "public" fields */
		jmp_buf setjmp_buffer;	/* for return to caller */
	};

	typedef struct my_error_mgr * my_error_ptr;
	METHODDEF(void)
	my_error_exit (j_common_ptr cinfo)
	{
		/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
		my_error_ptr myerr = (my_error_ptr) cinfo->err;

		char msg[JMSG_LENGTH_MAX];
		(*cinfo->err->format_message) (cinfo, msg);
		Error("Critcal libjpeg error: %s \n", msg);
		//(*cinfo->err->output_message) (cinfo);
		 /* Return control to the setjmp point */
		longjmp(myerr->setjmp_buffer, 1);
	}
};

// JPG format
static Spectrum *ReadImageJPG(const string &filename, int *xres, int *yres) {
	/* This struct contains the JPEG decompression parameters and pointers to
	* working space (which is allocated as needed by the JPEG library).
	*/
	struct jpeg_decompress_struct cinfo;
	/* We use our private extension JPEG error handler.
	* Note that this struct must live as long as the main JPEG parameter
	* struct, to avoid dangling-pointer problems.
	*/
	struct my_error_mgr jerr;
	/* More stuff */
	FILE * infile;		/* source file */
	// JSAMPARRAY buffer;		/* Output row buffer */
	int row_stride;		/* physical row width in output buffer */

	/* In this example we want to open the input file before doing anything else,
	* so that the setjmp() error recovery below can assume the file is open.
	* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
	* requires it in order to read binary files.
	*/

	if ((infile = fopen(filename.c_str(), "rb")) == NULL) {
		fprintf(stderr, "Unable to open JPEG file \"%s\" to read\n", filename.c_str());
		return NULL;
	}

	/* Step 1: allocate and initialize JPEG decompression object */

	/* We set up the normal JPEG error routines, then override error_exit. */
	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;
	/* Establish the setjmp return context for my_error_exit to use. */
	if (setjmp(jerr.setjmp_buffer)) {
		/* If we get here, the JPEG code has signaled an error.
		 * We need to clean up the JPEG object, close the input file, and return.
		 */
		jpeg_destroy_decompress(&cinfo);
		fclose(infile);
		return NULL;
	}
	/* Now we can initialize the JPEG decompression object. */
	jpeg_create_decompress(&cinfo);

	/* Step 2: specify data source (eg, a file) */

	jpeg_stdio_src(&cinfo, infile);

	/* Step 3: read file parameters with jpeg_read_header() */

	(void) jpeg_read_header(&cinfo, TRUE);
	/* We can ignore the return value from jpeg_read_header since
	*   (a) suspension is not possible with the stdio data source, and
	*   (b) we passed TRUE to reject a tables-only JPEG file as an error.
	* See libjpeg.txt for more info.
	*/

	/* Step 4: set parameters for decompression */

	/* In this example, we don't need to change any of the defaults set by
	* jpeg_read_header(), so we do nothing here.
	*/

	/* Step 5: Start decompressor */

	(void) jpeg_start_decompress(&cinfo);
	/* We can ignore the return value since suspension is not possible
	* with the stdio data source.
	*/

	/* Check the color space, for now, we only support grayscale and rgb 
	*/

	if(cinfo.out_color_space != JCS_GRAYSCALE && cinfo.out_color_space != JCS_RGB)
	{
	  Error("Only support GRAYSCALE and RGB format now\n");
	  jpeg_destroy_decompress(&cinfo);
	  fclose(infile);
	  return NULL;
	}

	/* We may need to do some setup of our own at this point before reading
	* the data.  After jpeg_start_decompress() we have the correct scaled
	* output image dimensions available, as well as the output colormap
	* if we asked for color quantization.
	* In this example, we need to make an output work buffer of the right size.
	*/ 
	/* JSAMPLEs per row in output buffer */
	row_stride = cinfo.output_width * cinfo.output_components;

	/* Make a one-row-high sample array that will go away when done with image */
	/*buffer = (*cinfo.mem->alloc_sarray)
		((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);*/

	// Allocate memory for storage
	*xres = cinfo.output_width;
	*yres = cinfo.output_height;
	int nChannels = cinfo.output_components;
	int size = *xres * *yres * nChannels;
	uint8_t **buffer = new uint8_t *[*yres];
	uint8_t *data = AllocAligned<uint8_t>(size);
	for(int i = 0; i < *yres; i++)
	  buffer[i] = data + i * row_stride;

	/* Step 6: while (scan lines remain to be read) */
	/*           jpeg_read_scanlines(...); */

	/* Here we use the library's state variable cinfo.output_scanline as the
	* loop counter, so that we don't have to keep track ourselves.
	*/
	int counter = 0;
	while (cinfo.output_scanline < cinfo.output_height) {
		/* jpeg_read_scanlines expects an array of pointers to scanlines.
		 * Here the array is only one element long, but you could ask for
		 * more than one scanline at a time if that's more convenient.
		 */
		counter += jpeg_read_scanlines(&cinfo, &buffer[counter], *yres);
	}

	/* Step 7: Finish decompression */

	(void) jpeg_finish_decompress(&cinfo);
	/* We can ignore the return value since suspension is not possible
	* with the stdio data source.
	*/

	/* Step 8: Release JPEG decompression object */

	/* This is an important step since it will release a good deal of memory. */
	jpeg_destroy_decompress(&cinfo);

	/* After finish_decompress, we can close the input file.
	* Here we postpone it until after no more JPEG errors are possible,
	* so as to simplify the setjmp error logic above.  (Actually, I don't
	* think that jpeg_destroy can do an error exit, but why assume anything...)
	*/
	fclose(infile);

	/* At this point you may want to check to see whether any corrupt-data
	* warnings occurred (test whether jerr.pub.num_warnings is nonzero).
	*/

	/* 
	 Convert data to spectrum.
	*/
	float *frgb = new float[size];
	for(int i = 0; i < size; i++)
	  frgb[i] = (float)data[i] * INV_255;
	Spectrum *ret = new Spectrum[*xres * *yres];
	if(nChannels == 1)	// GRAYSCALE
	  for(int i = 0;i < size; i++)
		  ret[i] = frgb[i];
	else {	// RGB
	 for (int i = 0; i < *xres * *yres; i++) {
		 ret[i].FromRGB(&frgb[3*i]);
	 }
	}

	FreeAligned(data);
	SAFE_DELETE_ARRAY(buffer);

	return ret;
}


static void WriteImageJPG(const string &filename, float *pixels, int xres, int yres) {
		
	if(pixels == NULL  || xres ==0 || yres == 0) {
		Warning("No Image Data to write\n");
		return;
	}
	// Allocate memory for rbg buffer
	int size = xres * yres * 3;
	int row_stride = xres * 3;
	uint8_t **buffer = new uint8_t *[size];
	uint8_t *data = AllocAligned<uint8_t>(size);
	for(int i = 0; i < yres; i++)
	   buffer[i] = data + i * row_stride;
	for (int i = 0;i < size; i++) {
		data[i] = TO_BYTE(pixels[i]);
	}

	/* This struct contains the JPEG compression parameters and pointers to
	* working space (which is allocated as needed by the JPEG library).
	* It is possible to have several such structures, representing multiple
	* compression/decompression processes, in existence at once.  We refer
	* to any one struct (and its associated working data) as a "JPEG object".
	*/
	struct jpeg_compress_struct cinfo;
	/* This struct represents a JPEG error handler.  It is declared separately
	* because applications often want to supply a specialized error handler
	* (see the second half of this file for an example).  But here we just
	* take the easy way out and use the standard error handler, which will
	* print a message on stderr and call exit() if compression fails.
	* Note that this struct must live as long as the main JPEG parameter
	* struct, to avoid dangling-pointer problems.
	*/
	struct jpeg_error_mgr jerr;
	/* More stuff */
	FILE * outfile;		/* target file */
	JSAMPROW row_pointer[1];	/* pointer to JSAMPLE row[s] */
	//int row_stride;		/* physical row width in image buffer */

	/* Step 1: allocate and initialize JPEG compression object */

	/* We have to set up the error handler first, in case the initialization
	* step fails.  (Unlikely, but it could happen if you are out of memory.)
	* This routine fills in the contents of struct jerr, and returns jerr's
	* address which we place into the link field in cinfo.
	*/
	cinfo.err = jpeg_std_error(&jerr);
	/* Now we can initialize the JPEG compression object. */
	jpeg_create_compress(&cinfo);

	/* Step 2: specify data destination (eg, a file) */
	/* Note: steps 2 and 3 can be done in either order. */

	/* Here we use the library-supplied code to send compressed data to a
	* stdio stream.  You can also write your own code to do something else.
	* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
	* requires it in order to write binary files.
	*/
	if ((outfile = fopen(filename.c_str(), "wb")) == NULL) {
		Error("Unable to open JPEG file \"%s\" to write\n", filename.c_str());
		//exit(1);
		return;
	}
	jpeg_stdio_dest(&cinfo, outfile);

	/* Step 3: set parameters for compression */

	/* First we supply a description of the input image.
	* Four fields of the cinfo struct must be filled in:
	*/
	cinfo.image_width = xres; 	/* image width and height, in pixels */
	cinfo.image_height = yres;
	cinfo.input_components = 3;		/* # of color components per pixel */
	cinfo.in_color_space = JCS_RGB; 	/* color space of input image */
	/* Now use the library's routine to set default compression parameters.
	* (You must set at least cinfo.in_color_space before calling this,
	* since the defaults depend on the source color space.)
	*/
	jpeg_set_defaults(&cinfo);
	/* Now you can set any non-default parameters you wish to.
	* Here we just illustrate the use of quality (quantization table) scaling:
	*/
	jpeg_set_quality(&cinfo, 80, TRUE /* limit to baseline-JPEG values */);

	/* Step 4: Start compressor */

	/* TRUE ensures that we will write a complete interchange-JPEG file.
	* Pass TRUE unless you are very sure of what you're doing.
	*/
	jpeg_start_compress(&cinfo, TRUE);

	/* Step 5: while (scan lines remain to be written) */
	/*           jpeg_write_scanlines(...); */

	/* Here we use the library's state variable cinfo.next_scanline as the
	* loop counter, so that we don't have to keep track ourselves.
	* To keep things simple, we pass one scanline per call; you can pass
	* more if you wish, though.
	*/
	//row_stride = image_width * 3;	/* JSAMPLEs per row in image_buffer */

	while (cinfo.next_scanline < cinfo.image_height) {
		/* jpeg_write_scanlines expects an array of pointers to scanlines.
		 * Here the array is only one element long, but you could pass
		 * more than one scanline at a time if that's more convenient.
		 */
		row_pointer[0] =  buffer[cinfo.next_scanline * row_stride];
		(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
	}

	/* Step 6: Finish compression */

	jpeg_finish_compress(&cinfo);
	/* After finish_compress, we can close the output file. */
	fclose(outfile);

	/* Step 7: release JPEG compression object */

	/* This is an important step since it will release a good deal of memory. */
	jpeg_destroy_compress(&cinfo);

	/* Free memory */
	FreeAligned(data);
	SAFE_DELETE_ARRAY(buffer);

	/* And we're done! */
}

#include<png.h>
#include <pnginfo.h>


// PNG format
static Spectrum *ReadImagePNG(const string &filename, int *xres, int *yres) {
	//png_structp png_ptr;
	//png_infop info_ptr;
	//volatile png_bytepp rows = NULL;

	///* Create buffers */
	//png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &png_error_func, NULL);
	//if (png_ptr == NULL) {
	//	Error("Error while creating PNG data structure\n");
	//}

	//info_ptr = png_create_info_struct(png_ptr);
	//if (info_ptr == NULL) {
	//	png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
	//	Error("Error while creating PNG information structure\n");
	//}

	///* Error handling */
	//if (setjmp(png_jmpbuf(png_ptr))) {
	//	png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
	//	if (rows)
	//		delete[] rows;
	//	Error("Error reading the PNG file\n");
	//}
	//FILE *fp = fopen(filename.c_str(),"rb");
	//if(!fp) {
	//	Error("Unable to open PNG file \"%s\" to read\n",filename.c_str());
	//}

	///* Set read helper function */
	//png_init_io(png_ptr, fp);

	//int bitdepth, colortype, interlacetype, compressiontype, filtertype;
	//png_read_info(png_ptr, info_ptr);
	//png_uint_32 width=0, height=0;
	//png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitdepth,
	//	&colortype, &interlacetype, &compressiontype, &filtertype);
	//int newDepth = bitdepth;

	//if (bitdepth == 1) {
	//	png_set_packing(png_ptr); // Unpack and later re-pack
	//} else if (colortype == PNG_COLOR_TYPE_PALETTE) {
	//	png_set_expand(png_ptr); // expand indexed files
	//	newDepth = 8;
	//} else if (colortype == PNG_COLOR_TYPE_GRAY && bitdepth < 8) {

	//	png_set_expand_gray_1_2_4_to_8(png_ptr); // convert grayscale to 8bit
	//	newDepth = 8;
	//} else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
	//	png_set_expand(png_ptr); // transparency
	//} else if (bitdepth < 8) {
	//	newDepth = 8;
	//	png_set_expand(png_ptr);
	//}

	//// handle interlacing
	//if (interlacetype != PNG_INTERLACE_NONE)
	//	png_set_interlace_handling(png_ptr);

	//int m_bpp;

	//if (bitdepth == 1) {
	//	m_bpp = 1; 
	//} else if (colortype == PNG_COLOR_TYPE_GRAY) {
	//	m_bpp = newDepth;
	//} else if (colortype == PNG_COLOR_TYPE_GRAY_ALPHA) {
	//	m_bpp = newDepth*2;
	//} else {
	//	m_bpp = newDepth * ((colortype & PNG_COLOR_MASK_ALPHA) ? 4 : 3);
	//}

	//double m_gamma;
	//int intent; double gamma;
	//if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
	//	m_gamma = -1;
	//} else if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
	//	m_gamma = (float) gamma;
	//} else {
	//	m_gamma = 1.0f/2.2f;
	//}

	//Debug("Reading %ix%ix%i PNG file", width, height, m_bpp);

	///* Update the information */
	//png_read_update_info(png_ptr, info_ptr);

	///* re-read */
	//png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitdepth,
	//	&colortype, &interlacetype, &compressiontype, &filtertype);
	//*xres = width; *yres = height;
	//int size;
	//uint8_t *data;
	//size = (size_t) std::ceil((*xres * *yres * m_bpp) / 8.0);
	//data = AllocAligned<uint8_t>(size);

	//rows = new png_bytep[*yres];

	//if (m_bpp == 1) {
	//	for (int i=0; i<*yres; i++)
	//		rows[i] = new unsigned char[*xres];
	//} else {
	//	int rowBytes = *xres * (m_bpp / 8);
	//	for (int i=0; i<*yres; i++)
	//		rows[i] = data + i * rowBytes;
	//}

	//png_read_image(png_ptr, rows);
	//png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);

	//if (m_bpp == 1) {
	//	// The bit mask has been unpacked by the decoding, now re-pack it
	//	memset(data, 0, size);
	//	for (int i=0; i<*yres; i++) {
	//		for (int o=0; o<*xres; o++) {
	//			int pos = i * *xres + o;
	//			data[pos / 8] |= rows[i][o] * (1 << (pos % 8));
	//		}
	//		//delete[] rows[i];
	//		//SAFE_DELETE_ARRAY(rows[i]);
	//	}
	//}
	//
	//fclose(fp);

	//float *frgb = new float[size];
	//for(int i = 0; i < size; i++)
	//	frgb[i] = (float)data[i] * INV_255;

	//Spectrum *ret = new Spectrum[*xres * *yres];
	//int totalRes = *xres * *yres;
	//
	//// This is still wrong ??
	//// rows[i][o] is 0/1...
	//// we will see in the test...
	//// We can use high-level read interface directly in fact..
	//if(m_bpp == 1) {
	//	// get data from rows[i][o]
	//	for(int i = 0; i < *yres; i++) {
	//		for(int o = 0; o < *xres; o++) {
	//			ret[i * *xres + o] = (float)rows[i][o] * INV_255;
	//		}
	//		SAFE_DELETE_ARRAY(rows[i]);
	//	}
	//}
	//else if(m_bpp == 24) {
	//	for(int i = 0; i < totalRes; i++)
	//		ret[i].FromRGB(&frgb[3*i]);
	//}
	//else if(m_bpp == 32) {
	//	for(int i = 0; i < totalRes; i++)
	//		ret[i].FromRGB(&frgb[4*i]);
	//}
	//else { 
	//	Error("I don't know the format\n");
	//	return NULL;
	//}

	//FreeAligned(data);
	//SAFE_DELETE_ARRAY(rows);

	//return ret;

	

	// Another implementation

	
	//png_structp png_ptr = NULL;
	//png_infop info_ptr = NULL;
	//png_bytep *row_pointers = NULL;
	//int bitDepth, colourType;

	//FILE *pngFile = fopen(filename.c_str(), "rb");
	//if(!pngFile) {
	//	Error("Unable to open PNG file \"%s\" to read\n",filename.c_str());
	//	return NULL;
	//}

	//png_byte sig[8];

	//fread(&sig, 8, sizeof(png_byte), pngFile);
	////so when we init io it won't bitch
	//rewind(pngFile);
	//if(!png_check_sig(sig, 8))
	//	return NULL;

	//png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);

	//if(!png_ptr)
	//	return NULL;

	//if(setjmp(png_jmpbuf(png_ptr)))
	//	return NULL;

	//info_ptr = png_create_info_struct(png_ptr);

	//if(!info_ptr)
	//	return NULL;

	//png_init_io(png_ptr, pngFile);

	//png_read_info(png_ptr, info_ptr);

	//bitDepth = png_get_bit_depth(png_ptr, info_ptr);

	//colourType = png_get_color_type(png_ptr, info_ptr);

	//if(colourType == PNG_COLOR_TYPE_PALETTE)
	//	png_set_palette_to_rgb(png_ptr);

	//if(colourType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
	//	//png_set_gray_1_2_4_to_8(png_ptr);
	//	//rename
	//	png_set_expand_gray_1_2_4_to_8(png_ptr);

	//if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
	//	png_set_tRNS_to_alpha(png_ptr);

	//if(bitDepth == 16)
	//	png_set_strip_16(png_ptr);
	//else if(bitDepth < 8)
	//	png_set_packing(png_ptr);

	//png_read_update_info(png_ptr, info_ptr);

	//png_uint_32 width, height;
	//png_get_IHDR(png_ptr, info_ptr, &width, &height,
	//	&bitDepth, &colourType, NULL, NULL, NULL);

	//int components;
	//// Get components
	//switch(colourType)
	//{
	//	case PNG_COLOR_TYPE_GRAY:
	//		components = 1;
	//		break;
	//	case PNG_COLOR_TYPE_GRAY_ALPHA:
	//		components = 2;
	//		break;
	//	case PNG_COLOR_TYPE_RGB:
	//		components = 3;
	//		break;
	//	case PNG_COLOR_TYPE_RGB_ALPHA:
	//		components = 4;
	//		break;
	//	default:
	//		components = -1;//fucked
	//};
	//
	//if(components == -1)
	//{
	//	if(png_ptr)
	//		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	//	Error("Only support gray and rgb color type PNG file\n");
	//	return NULL;
	//}

	//uint8_t *data = AllocAligned<uint8_t>(width * height * components);

	//row_pointers = new (png_bytep *)[height];

	//for(int i = 0; i < height; ++i)
	//	row_pointers[i] = (png_bytep)(data + (i * width * components));

	//png_read_image(png_ptr, row_pointers);
	//png_read_end(png_ptr, NULL);



	//png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

	//fclose(pngFile);

	//// Create float rgb
	//float *frgb = new float[width * height * components];
	//for(int i = 0; i < width * height * components; i++)
	//	frgb[i] = (float)data[i] * INV_255;
	//// Create Spectrum
	//Spectrum *ret = new Spectrum[width * height];
	//if(components == 1 || components == 2) {
	//	for(int i = 0; i < width * height; i++)
	//		ret[i] = frgb[i*components];
	//} else {
	//	for(int i = 0; i < width * height; i++)
	//		ret[i].FromRGB(&frgb[i*components]);
	//}

	//// Free memory
	//SAFE_DELETE_ARRAY(frgb);
	//FreeAligned(data);
	//SAFE_DELETE_ARRAY(row_pointers);


	//// Set resolution of the image
	//*xres = width;
	//*yres = height;

	//return ret;

	// Version 3
	// Using high-level read interface
	//char png_header[8];
 //   png_structp png_ptr;
 //   png_infop info_ptr;
 //   int width, height;// rowBytes;
 //   png_byte color_type; 
 //   //png_byte bit_depth;
 //   //png_colorp palette; 

 //   /* open file and test for it being a png */
 //   FILE *file = fopen(filename.c_str(), "rb");
	//if(!file) {
	//	Error("Unable to open PNG file \"%s\" to read\n",filename.c_str());
	//	return NULL;
	//}
 //   fread(png_header, 1, 8, file);
 //   if(png_sig_cmp((png_bytep)png_header, 0, 8))
 //   {
 //       Error("Not a PNG file...\n");
 //       fclose(file);
	//	return NULL;
 //   }
 //   /* initialise structures for reading a png file */
 //   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
 //   info_ptr = png_create_info_struct(png_ptr);
 //   if (setjmp(png_jmpbuf(png_ptr)))
 //   {
 //       png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 //       Error("ReadPngFile: Failed to read the PNG file");
 //       fclose(file);
	//	return NULL;
 //   }
 //   //I/O initialisation methods
 //   png_init_io(png_ptr, file);
 //   png_set_sig_bytes(png_ptr, 8);  //Required!!! or we can rewind the file


 //   /* **************************************************
 //    * The high-level read interface in libpng (http://www.libpng.org/pub/png/libpng-1.2.5-manual.html)
 //    * **************************************************
 //    */
 //   png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND|PNG_TRANSFORM_STRIP_ALPHA, 0);
 //   width = info_ptr->width;
 //   height = info_ptr->height;
	//color_type = info_ptr->color_type;
	//int ncomponents;
	//if(color_type == PNG_COLOR_TYPE_GRAY)
	//	ncomponents = 1;
	//else ncomponents = 3;


 //   float* frgb = new float[width * height * ncomponents];  //each pixel(RGBA) has 3 bytes
 //   png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);

 //   int pos = 0;
	//if(ncomponents == 1) {
	//	for(int row = 0; row < height; row++) {
	//		for(int col = 0; col < width; col++)
	//			frgb[pos++] = (float)row_pointers[row][col] * INV_255;
	//	}
	//}
 //   else for(int row = 0; row < height; row++) {
 //       for(int col = 0; col < (3 * width); col += 3) {
 //           frgb[pos++] = (float)row_pointers[row][col] * INV_255;     // red
 //           frgb[pos++] = (float)row_pointers[row][col + 1] * INV_255; // green
 //           frgb[pos++] = (float)row_pointers[row][col + 2] * INV_255; // blue
 //       }
 //   }

	//Spectrum *ret = new Spectrum[width * height *ncomponents];
	//if(ncomponents == 1)
	//	for(int i = 0; i < width * height; i++)
	//		ret[i] = frgb[i];
	//else
	//	for(int i = 0; i < width * height; i++)
	//		ret[i].FromRGB(&frgb[3*i]);

	//// free memory
	//png_destroy_read_struct(&png_ptr, &info_ptr, 0);
	//
	//SAFE_DELETE_ARRAY(frgb);

	//// close file
	//fclose(file);

	//// Set resolution of the image
	//*xres = width;
	//*yres = height;

	//return ret;

	// This version is tested!!
	FILE *fp = fopen(filename.c_str(), "rb");
	if (!fp) {
		Error("Unable to open file \"%s\" to read\n", filename.c_str());
		return NULL;
	}
	// read the header
	const size_t HEADER_LENGTH = 8;
	png_byte header[HEADER_LENGTH];
	size_t n = fread(header, 1, HEADER_LENGTH, fp);
	if (n != HEADER_LENGTH || png_sig_cmp(header, 0, HEADER_LENGTH))
		return NULL;
	// try to create the loading structures
	png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0,
		0);
	if (!png_ptr) {
		fclose(fp);
		return NULL;
	}

	png_infop info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, (png_infopp) 0, (png_infopp) 0);
		fclose(fp);
		return NULL;
	}

	png_infop end_info = png_create_info_struct(png_ptr);
	if (!end_info) {
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
		fclose(fp);
		return NULL;
	}

	if (setjmp(png_jmpbuf(png_ptr))) {
		png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		fclose(fp);
		return NULL;
	}

	// start the io
	png_init_io(png_ptr, fp);

	// indicate that we have already read some of the hearder
	png_set_sig_bytes(png_ptr, HEADER_LENGTH);

	// read the image info, get some info
	png_read_info(png_ptr, info_ptr);
	*xres = png_get_image_width(png_ptr, info_ptr);
	*yres = png_get_image_height(png_ptr, info_ptr);
	int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
	png_byte color_type = png_get_color_type(png_ptr, info_ptr);

	// force the image into RGBA, 8 bits per channel
	if (color_type != PNG_COLOR_TYPE_RGBA)
		png_set_expand(png_ptr);
	if (color_type == PNG_COLOR_TYPE_GRAY ||
		color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr);
	if (bit_depth < 8)
		png_set_packing(png_ptr);
	else if (bit_depth == 16)
		png_set_strip_16(png_ptr);
	if (color_type != PNG_COLOR_TYPE_RGBA)
		png_set_filler(png_ptr, 255, PNG_FILLER_AFTER);
	png_read_update_info(png_ptr, info_ptr);

	// make sure we're actually in rgba mode
	if ((int)png_get_rowbytes(png_ptr, info_ptr) != ((*xres) * 4)) {
		png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		fclose(fp);
		return NULL;
	}

	// finally, read the file
	uint8_t *buffer = AllocAligned<uint8_t>((*xres) * (*yres) * 4);
	png_bytep *row_pointers = new png_bytep[*yres];
	for (int y = 0 ; y < (*yres) ; y++) {
		row_pointers[y] = (png_byte *) (buffer + ((*yres) - 1 - y) *
			(*xres) * 4);
	}
	png_read_rows(png_ptr, row_pointers, 0, (long unsigned int) (*yres));

	// deallocate memory and return
	fclose(fp);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Convert buffer to Spectrum
	int size = *xres * *yres;
	Spectrum *ret = new Spectrum[size];
	if(!ret) {
		Error("Not enough memory for spectrum in reading png file\n");
		return NULL;
	}

	for(int i = 0; i < size; i++)
		ret[i].FromRGB(&buffer[4*i]);

	FreeAligned(buffer);
	SAFE_DELETE_ARRAY(row_pointers);

	return ret;
}

static void WriteImagePNG(const string &filename, float *pixels, int xres, int yres) {
	
	if(pixels == NULL || xres ==0 || yres == 0) {
		Warning("No Image Data to write\n");
		return;
	}

	FILE *fp = fopen(filename.c_str(),"wb");
	if(!fp) {
		Error("Unable to open PNG file \"%s\" to write\n",filename.c_str());
		return;
	}

	// create the needed data structures
	png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,
		0);
	if (!png_ptr) {
		fclose(fp);
		return;
	}
	png_infop info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		fclose(fp);
		png_destroy_write_struct(&png_ptr, (png_infopp) 0);
		return;
	}

	// do the setjmp thingy
	if (setjmp(png_jmpbuf(png_ptr))) {
		png_destroy_write_struct(&png_ptr, &info_ptr);
		fclose(fp);
		return;
	}

	// set up the io
	png_init_io(png_ptr, fp);

	// write the header
	png_set_IHDR(png_ptr, info_ptr, xres, yres, 8, PNG_COLOR_TYPE_RGB_ALPHA,
		PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	png_write_info(png_ptr, info_ptr);
	png_set_flush(png_ptr, 10);

	// convert float value into bytes

	uint8_t *buffer = AllocAligned<uint8_t>(xres*yres*4);

	for(int i = 0; i < xres*yres; i += 4) {
		buffer[4*i  ] = TO_BYTE(pixels[3*i  ]);
		buffer[4*i+1] = TO_BYTE(pixels[3*i+1]);
		buffer[4*i+2] = TO_BYTE(pixels[3*i+2]);
		buffer[4*i+3] = 1;
	}


	// write the image
	png_bytep *row_pointers = new png_bytep[yres];
	for (int y = 0 ; y < yres ; y++)
		row_pointers[y] = (png_byte *) (buffer + (yres - 1 - y) * xres * 4);
	png_write_image(png_ptr, row_pointers);
	png_write_end(png_ptr, info_ptr);

	// clean up memory, and finish
	fclose(fp);
	png_destroy_write_struct(&png_ptr, &info_ptr);

	FreeAligned(buffer);
	SAFE_DELETE_ARRAY(row_pointers);
	
}

#include <tiffio.h>

// TIFF format
static Spectrum *ReadImageTIF(const string &filename, int *xres, int *yres) {
	// Try to open TIFF file
	TIFF *tiff = TIFFOpen(filename.c_str(), "r");
	if (!tiff) {
		Error("Unable to open TIFF \"%s\" to read", filename.c_str());
		return NULL;
	}
	// Get basic information from TIFF header
	short int nSamples;
	int xSize, ySize;
	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &xSize);
	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &ySize);
	TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &nSamples);
	if (nSamples != 3 && nSamples != 4) {
		Error("Sorry, only handle 3 and 4 sample TIFFs...\n");
		return NULL;
	}
	bool hasAlpha = (nSamples == 4);
	*xres = xSize;
	*yres = ySize;

	// Make sure this is a TIFF we can read
	short int bitsPerSample, sampleFormat = SAMPLEFORMAT_UINT;
	if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
		Error("TIFFRead: bits per sample not set in TIFF\n");
		TIFFClose(tiff);
		return NULL;
	}

	if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
		if (bitsPerSample == 32)
			sampleFormat = SAMPLEFORMAT_IEEEFP;
		else
			sampleFormat = SAMPLEFORMAT_UINT;
	}

	if (bitsPerSample == 32) {
		if (sampleFormat != SAMPLEFORMAT_IEEEFP) {
			Error("TIFFRead: 32 bit TIFF not stored in floating point format\n");
			TIFFClose(tiff);
			return NULL;
		}
	}
	else {
		if (bitsPerSample != 8 && bitsPerSample != 32) {
			Error("TIFFRead: only 8 and 32 bits per sample supported\n");
			TIFFClose(tiff);
			return NULL;
		}
		if (sampleFormat != SAMPLEFORMAT_UINT) {
			Error("TIFFRead: 8 bit TIFFs must be stored as unsigned ints\n");
			TIFFClose(tiff);
			return NULL;
		}
	}

	int bytesPerSample = bitsPerSample / 8;
	if (nSamples * *xres * bytesPerSample != TIFFScanlineSize(tiff)) {
		Error("TIFFRead: RGB not interleaved in TIFF %s\n", filename.c_str());
		TIFFClose(tiff);
		return NULL;
	}

	// Allocate space for [[pixels]] and buffers
	float *rgba = new float[nSamples * *xres * *yres];
	float *p = rgba;
	unsigned char *ubuf = NULL;
	if (bitsPerSample == 8) ubuf = new unsigned char[nSamples * *xres];

	for (int y = 0; y < *yres; ++y) {
		if (ubuf) {
			// Read 8-bit TIFF scanline
			if (TIFFReadScanline(tiff, ubuf, y, 1) == -1) {
				TIFFClose(tiff);
				return NULL;
			}
			for (int x = 0; x < nSamples* *xres; ++x)
				*p++ = ubuf[x] * INV_255;
		}
		else {
			// Read floating point TIFF scanline
			if (TIFFReadScanline(tiff, p, y, 1) == -1) {
				TIFFClose(tiff);
				return NULL;
			}
			p += nSamples * *xres;
		}
	}

	//delete[] ubuf;
	SAFE_DELETE_ARRAY(ubuf);

	TIFFClose(tiff);

	// Convert rgba to spectrum
	Spectrum *ret = new Spectrum[*xres * *yres];

	if(nSamples == 3) {
		for(int i = 0; i < *xres * *yres; i++)
			ret[i].FromRGB(&rgba[3*i]);
	}
	else if(nSamples == 4) {
		for(int i = 0; i < *xres * *yres; i++)
			ret[i].FromRGB(&rgba[4*i]);
	}
	else { 
		Error("I don't know the format\n");
		return NULL;
	}

	SAFE_DELETE_ARRAY(rgba);

	return ret;
}
static void WriteImageTIF(const string &filename, float *pixels, int xres, int yres) {
	
	if(pixels == NULL || xres ==0 || yres == 0) {
		Warning("No Image Data to write\n");
		return;
	}

	// Open 8-bit TIFF file for writing
	TIFF *tiff = TIFFOpen(filename.c_str(), "w");
	if (!tiff) {
		Error("Unable to open TIFF %s to write", filename.c_str());
		return;
	}
	// You may want to change these
	bool hasAlpha = false;
	int nChannels =  3;
	
	TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, nChannels);
	if (hasAlpha) {
		short int extra[] = { EXTRASAMPLE_ASSOCALPHA };
		TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, (short)1, extra);
	}
	// Write image resolution information
	TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, xres);
	TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, yres);
	TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8);
	TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
	// Set Generic TIFF Fields
	TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1);
	TIFFSetField(tiff, TIFFTAG_XRESOLUTION, 1.f);
	TIFFSetField(tiff, TIFFTAG_YRESOLUTION, 1.f);
	TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, (short)1);
	TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
	TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField(tiff, TIFFTAG_ORIENTATION, (int)ORIENTATION_TOPLEFT);

	// Convert from float to unsigned char
	int size = xres * yres * nChannels;
	uint8_t *frgb = new uint8_t[size];
	for(int i = 0; i < size; i++)
		frgb[i] = TO_BYTE(pixels[i]);

	// Write 8-bit scanlines
	unsigned char *buf = new unsigned char[nChannels * xres];
	for (int y = 0; y < yres; ++y) {
		unsigned char *bufp = buf;
		for (int x = 0; x < xres; ++x) {
			// Pack 8-bit pixels samples into buf
			for (int s = 0; s < nChannels; ++s)
				*bufp++ = (unsigned char)*frgb++;
		}
		TIFFWriteScanline(tiff, buf, y, 1);
	}
	// Close 8-bit TIFF file
	SAFE_DELETE_ARRAY(buf);
	SAFE_DELETE_ARRAY(frgb);

	//delete[] buf;
	
	TIFFClose(tiff);
}


// RGBE HDR format
// I like this one best...

#include <rgbe.h>

// HDR format
Spectrum *ReadImageHDR(const string &filename, int *xres, int *yres) {

	FILE *f = fopen(filename.c_str(),"rb");
	if(!f) {
		Error("Unable to open file \"%s\" to read\n",filename.c_str());
		return NULL;
	}
	RGBE_ReadHeader(f, xres, yres, NULL);
	float *frgb = new float[3* *xres * *yres];
	RGBE_ReadPixels_RLE(f, frgb, *xres, *yres);
	fclose(f);

	int size = *xres * *yres;
	Spectrum *ret = new Spectrum[size];
	for(int i = 0; i < size; i++) {
		ret[i].FromRGB(&frgb[3*i]);
	}

	SAFE_DELETE_ARRAY(frgb);

	return ret;
}

void WriteImageHDR(const string &filename, float *pixels, int xres, int yres) {
	
	if(pixels == NULL || xres == 0 || yres ==0) {
		Error("No data to write\n");
	}
	
	FILE *f = fopen(filename.c_str(),"wb");
	if(!f) {
		Error("Unable to open file \"%s\" to write\n",filename.c_str());
		return ;
	}

	RGBE_WriteHeader(f, xres, yres, NULL);
	RGBE_WritePixels_RLE(f, pixels, xres, yres);

	fclose(f);
}